﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LibNoise.Model;

namespace LibNoise.Utilities {
	/// Builds a planar noise map.
    ///
    /// This class builds a noise map by filling it with coherent-noise values
    /// generated from the surface of a plane.
    ///
    /// This class describes these input values using (x, z) coordinates.
    /// Their y coordinates are always 0.0.
    ///
    /// The application must provide the lower and upper x coordinate bounds
    /// of the noise map, in units, and the lower and upper z coordinate
    /// bounds of the noise map, in units.
    ///
    /// To make a tileable noise map with no seams at the edges, call the
    /// EnableSeamless() method.
    public class NoiseMapBuilderPlane : NoiseMapBuilder
    {
		#region Private Members
		/// A flag specifying whether seamless tiling is enabled.
        bool m_isSeamlessEnabled;

        /// Lower x boundary of the planar noise map, in units.
        double m_lowerXBound;

        /// Lower z boundary of the planar noise map, in units.
        double m_lowerZBound;

        /// Upper x boundary of the planar noise map, in units.
        double m_upperXBound;

        /// Upper z boundary of the planar noise map, in units.
        double m_upperZBound;
#endregion

        /// Constructor.
		public NoiseMapBuilderPlane() {
			m_isSeamlessEnabled = false;
			m_lowerXBound = 0.0;
			m_lowerZBound = 0.0;
			m_upperXBound = 0.0;
			m_upperZBound = 0.0;
		}

        public override void Build (){
			if ( m_upperXBound <= m_lowerXBound || m_upperZBound <= m_lowerZBound
					|| m_destWidth <= 0 || m_destHeight <= 0
					|| m_pSourceModule == null || m_pDestNoiseMap == null) {
				throw new Exception("Invalid Parameter");
			}

			// Resize the destination noise map so that it can store the new output
			// values from the source model.
			m_pDestNoiseMap.SetSize(m_destWidth, m_destHeight);

			// Create the plane model.
			Plane planeModel = new Plane();
			planeModel.SetModule(m_pSourceModule);

			double xExtent = m_upperXBound - m_lowerXBound;
			double zExtent = m_upperZBound - m_lowerZBound;
			double xDelta  = xExtent / (double)m_destWidth ;
			double zDelta  = zExtent / (double)m_destHeight;
			double xCur    = m_lowerXBound;
			double zCur    = m_lowerZBound;

				// Fill every point in the noise map with the output values from the model.
			for (int z = 0; z < m_destHeight; z++) {
				xCur = m_lowerXBound;
				for (int x = 0; x < m_destWidth; x++) {
					float pDest = m_pDestNoiseMap.GetSlabPtr(x, z);
					float finalValue;
					if (!m_isSeamlessEnabled) {
						finalValue = (float)planeModel.GetValue(xCur, zCur);
					} else {
						double swValue, seValue, nwValue, neValue;
						swValue = planeModel.GetValue (xCur          , zCur          );
						seValue = planeModel.GetValue (xCur + xExtent, zCur          );
						nwValue = planeModel.GetValue (xCur          , zCur + zExtent);
						neValue = planeModel.GetValue (xCur + xExtent, zCur + zExtent);
						double xBlend = 1.0 - ((xCur - m_lowerXBound) / xExtent);
						double zBlend = 1.0 - ((zCur - m_lowerZBound) / zExtent);
						double z0 = Utils.LinearInterp (swValue, seValue, xBlend);
						double z1 = Utils.LinearInterp(nwValue, neValue, xBlend);
						finalValue = (float)Utils.LinearInterp(z0, z1, zBlend);
					}
					m_pDestNoiseMap.SetSlabPtr(x, z, finalValue);
					xCur += xDelta;
				}
				zCur += zDelta;
				if (m_pCallback != null) {
					m_pCallback(z);
				}
			}
		}

        /// Enables or disables seamless tiling.
        ///
        /// @param enable A flag that enables or disables seamless tiling.
        ///
        /// Enabling seamless tiling builds a noise map with no seams at the
        /// edges.  This allows the noise map to be tileable.
        public void EnableSeamless (bool enable = true)
        {
          m_isSeamlessEnabled = enable;
        }

        /// Returns the lower x boundary of the planar noise map.
        ///
        /// @returns The lower x boundary of the planar noise map, in units.
        public double GetLowerXBound ()
        {
          return m_lowerXBound;
        }

        /// Returns the lower z boundary of the planar noise map.
        ///
        /// @returns The lower z boundary of the noise map, in units.
        public double GetLowerZBound ()
        {
          return m_lowerZBound;
        }

        /// Returns the upper x boundary of the planar noise map.
        ///
        /// @returns The upper x boundary of the noise map, in units.
        public double GetUpperXBound ()
        {
          return m_upperXBound;
        }

        /// Returns the upper z boundary of the planar noise map.
        ///
        /// @returns The upper z boundary of the noise map, in units.
        public double GetUpperZBound ()
        {
          return m_upperZBound;
        }

        /// Determines if seamless tiling is enabled.
        ///
        /// @returns
        /// - @a true if seamless tiling is enabled.
        /// - @a false if seamless tiling is disabled.
        ///
        /// Enabling seamless tiling builds a noise map with no seams at the
        /// edges.  This allows the noise map to be tileable.
        public bool IsSeamlessEnabled ()
        {
          return m_isSeamlessEnabled;
        }

        /// Sets the boundaries of the planar noise map.
        ///
        /// @param lowerXBound The lower x boundary of the noise map, in
        /// units.
        /// @param upperXBound The upper x boundary of the noise map, in
        /// units.
        /// @param lowerZBound The lower z boundary of the noise map, in
        /// units.
        /// @param upperZBound The upper z boundary of the noise map, in
        /// units.
        ///
        /// @pre The lower x boundary is less than the upper x boundary.
        /// @pre The lower z boundary is less than the upper z boundary.
        ///
        /// @throw noise::ExceptionInvalidParam See the preconditions.
        public void SetBounds(double lowerXBound, double upperXBound, double lowerZBound, double upperZBound)
        {
			if (lowerXBound >= upperXBound || lowerZBound >= upperZBound) {
				throw new Exception("Invalid Parameter");
			}

			m_lowerXBound = lowerXBound;
			m_upperXBound = upperXBound;
			m_lowerZBound = lowerZBound;
			m_upperZBound = upperZBound;
        }
    };
}
